package com.ruyuan.careerplan.cookbook.service.impl;

import com.ruyuan.careerplan.common.cache.CacheSupport;
import com.ruyuan.careerplan.common.exception.BaseBizException;
import com.ruyuan.careerplan.common.redis.RedisCache;
import com.ruyuan.careerplan.common.redis.RedisLock;
import com.ruyuan.careerplan.common.utils.JsonUtil;
import com.ruyuan.careerplan.cookbook.constants.RedisKeyConstants;
import com.ruyuan.careerplan.cookbook.converter.CookbookUserConverter;
import com.ruyuan.careerplan.cookbook.dao.CookbookUserDAO;
import com.ruyuan.careerplan.cookbook.domain.dto.CookbookUserDTO;
import com.ruyuan.careerplan.cookbook.domain.dto.SaveOrUpdateUserDTO;
import com.ruyuan.careerplan.cookbook.domain.entity.CookbookUserDO;
import com.ruyuan.careerplan.cookbook.domain.request.CookbookUserQueryRequest;
import com.ruyuan.careerplan.cookbook.domain.request.SaveOrUpdateUserRequest;
import com.ruyuan.careerplan.cookbook.service.CookbookUserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.Objects;
import java.util.concurrent.TimeUnit;

/**
 * 菜谱作者服务
 *
 * @author zhonghuashishan
 */
@Slf4j
@Service
public class CookbookUserServiceImpl implements CookbookUserService {

    @Autowired
    private CookbookUserDAO cookbookUserDAO;

    @Autowired
    private CookbookUserConverter cookbookUserConverter;

    @Autowired
    private RedisCache redisCache;

    @Autowired
    private RedisLock redisLock;

    @Override
    public SaveOrUpdateUserDTO saveOrUpdateUser(SaveOrUpdateUserRequest request) {

        String userUpdateLockKey = RedisKeyConstants.USER_UPDATE_LOCK_PREFIX + request.getOperator();
        boolean lock = redisLock.lock(userUpdateLockKey);

        if (!lock) {
            log.info("操作作者信息获取锁失败，operator:{}", request.getOperator());
            throw new BaseBizException("新增/修改失败");
        }
        try {
            CookbookUserDO cookbookUserDO = cookbookUserConverter.convertCookbookUserDO(request);
            cookbookUserDO.setUpdateUser(request.getOperator());
            if (Objects.isNull(cookbookUserDO.getId())) {
                cookbookUserDO.setCreateUser(request.getOperator());
            }
            cookbookUserDAO.saveOrUpdate(cookbookUserDO);

            CookbookUserDTO cookbookUserDTO = cookbookUserConverter.convertCookbookUserDTO(cookbookUserDO);

            redisCache.setCache(RedisKeyConstants.USER_INFO_PREFIX + cookbookUserDO.getId(), cookbookUserDTO,
                    CacheSupport.generateCacheExpireSecond());

            SaveOrUpdateUserDTO dto = SaveOrUpdateUserDTO.builder()
                    .userId(cookbookUserDO.getId())
                    .success(true)
                    .build();
            return dto;
        } finally {
            redisLock.unlock(userUpdateLockKey);
        }
    }

    @Override
    public CookbookUserDTO getUserInfo(CookbookUserQueryRequest request) {
        Long userId = request.getUserId();

        String userInfoKey = RedisKeyConstants.USER_INFO_PREFIX + userId;
        // 从内存或者缓存中获取数据
        Object userInfoValue = redisCache.getCache(userInfoKey);

        if (Objects.equals(CacheSupport.EMPTY_CACHE, userInfoValue)) {
            // 如果是空缓存，则是防止缓存穿透的，直接返回null
            return null;
        } else if (userInfoValue instanceof CookbookUserDTO) {
            // 如果是对象，则是从内存中获取到的数据，直接返回
            return (CookbookUserDTO) userInfoValue;
        } else if (userInfoValue instanceof String) {
            // 如果是字符串，则是从缓存中获取到的数据，转换成对象之后返回

            Long expire = redisCache.getExpire(userInfoKey, TimeUnit.SECONDS);
            /*
             * 对于临期缓存两种做法：
             * 1、临期再续期
             * 2、不续期，随机过期时间，过期了直接加锁查库，然后放入缓存
             * 这里采用第一种做法，如果缓存过期时间小于一小时，则重新设置过期时间
             */
            if (expire < CacheSupport.ONE_HOURS_SECONDS) {
                redisCache.expire(userInfoKey, CacheSupport.generateCacheExpireSecond());
            }
            return JsonUtil.json2Object((String) userInfoValue, CookbookUserDTO.class);
        }

        // 未在内存和缓存中获取到值，从数据库中获取
        return getUserInfoFromDB(userId);
    }

    private CookbookUserDTO getUserInfoFromDB(Long userId) {
        String userLockKey = RedisKeyConstants.USER_LOCK_PREFIX + userId;
        boolean lock = redisLock.lock(userLockKey);

        if (!lock) {
            log.info("缓存数据为空，从数据库查询作者信息时获取锁失败，userId:{}", userId);
            throw new BaseBizException("查询失败");
        }
        try {
            log.info("缓存数据为空，从数据库中获取数据，userId:{}", userId);
            String userInfoKey = RedisKeyConstants.USER_INFO_PREFIX + userId;
            CookbookUserDO cookbookUserDO = cookbookUserDAO.getById(userId);
            if (Objects.isNull(cookbookUserDO)) {
                redisCache.setCache(userInfoKey, CacheSupport.EMPTY_CACHE, CacheSupport.generateCachePenetrationExpireSecond());
                return null;
            }

            CookbookUserDTO dto = cookbookUserConverter.convertCookbookUserDTO(cookbookUserDO);

            redisCache.setCache(userInfoKey, dto, CacheSupport.generateCacheExpireSecond());
            return dto;
        } finally {
            redisLock.unlock(userLockKey);
        }
    }
}
